package com.yq.ylmall.wechat.service.impl;

import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.yq.ylmall.client.order.OrderFeignClient;
import com.yq.ylmall.dto.order.OrderInfoDto;
import com.yq.ylmall.dto.order.OrderItemDto;
import com.yq.ylmall.dto.order.PaymentInfoDto;
import com.yq.ylmall.enums.payment.OrderStatus;
import com.yq.ylmall.enums.payment.wechatpay.WechatApiType;
import com.yq.ylmall.enums.payment.wechatpay.WechatNotifyType;
import com.yq.ylmall.enums.payment.wechatpay.WechatTradeState;
import com.yq.ylmall.exception.ServiceException;
import com.yq.ylmall.model.order.OrderInfo;
import com.yq.ylmall.utils.OrderCodeUtil;
import com.yq.ylmall.wechat.config.WechatPayConfig;
import com.yq.ylmall.wechat.entity.RefundReason;
import com.yq.ylmall.wechat.service.WechatPayService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.rmi.ServerException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Project: yourlife
 * @Name: WechatServiceImpl
 * @Author: Jack_Li
 * @Date: 2024/6/13 15:50
 */

@Service
@Slf4j
public class WechatPayServiceImpl implements WechatPayService {

    private ReentrantLock lock = new ReentrantLock();

    @Resource
    private WechatPayConfig wechatPayConfig;

    @Resource
    private CloseableHttpClient wechatPayClient;

    @Resource
    private OrderFeignClient orderFeignClient;

    /**
     * 返回结果 code_url 和 订单号
     * @param orderNo
     * @return
     * @throws IOException
     */
    @Override
    @Transactional(rollbackFor = ServiceException.class)
    public Map<String, Object> nativePay(String orderNo) throws IOException {
        // 生成订单
        OrderInfoDto orderInfoDto = new OrderInfoDto();
        List<OrderItemDto> orderItems = new ArrayList<>();
        OrderItemDto orderItemDto = new OrderItemDto();
        orderInfoDto.setOrderNo(OrderCodeUtil.generateUniqueKey());
        orderInfoDto.setTotalAmount(new BigDecimal("1.00"));
        orderInfoDto.setOriginalTotalAmount(new BigDecimal("1.00"));
        orderInfoDto.setUserId(1L);
        orderItemDto.setProductId(1L);
        orderItemDto.setSkuId("0_0");
        orderItemDto.setSkuNum(1);
        orderItemDto.setSkuName("测试商品");
        orderItemDto.setSkuPrice(new BigDecimal("100"));
        orderItems.add(orderItemDto);
        orderInfoDto.setOrderItemList(orderItems);
        // 调用统一下单 API
        HttpPost httpPost = new HttpPost(wechatPayConfig.getDomain().concat(WechatApiType.NATIVE_PAY.getType()));
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wechatPayConfig.getAppid());
        paramsMap.put("mchid", wechatPayConfig.getMchId());
        paramsMap.put("description", orderItemDto.getSkuName());
        paramsMap.put("out_trade_no", orderInfoDto.getOrderNo());
        paramsMap.put("notify_url", wechatPayConfig.getNotifyDomain().concat(WechatNotifyType.NATIVE_NOTIFY.getType()));

        Map amountMap = new HashMap();
        // final int total = orderInfoDto.getTotalAmount().multiply(new BigDecimal("100")).setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
        amountMap.put("total", new BigDecimal(String.valueOf(orderInfoDto.getTotalAmount())).movePointRight(2).intValue());
        amountMap.put("currency", "CNY");

        paramsMap.put("amount", amountMap);

        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数为：{}", jsonParams);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");

        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpResponse response = wechatPayClient.execute(httpPost);

        try {

            String bodyAsString = EntityUtils.toString(response.getEntity());
            log.info("响应结果为：{}", bodyAsString);
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode == 200) {
                log.info("微信支付成功: {}", bodyAsString);
            } else if(statusCode == 204) {
                log.info("微信支付成功");
            } else {
                log.error("failed, resp code = {}, return body = {}", statusCode, bodyAsString);
                throw new ServerException("failed");
            }

            // 响应结果
            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            // 二维码
            String codeUrl = resultMap.get("code_url");
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfoDto.getOrderNo());
            return map;
        } finally {
            response.close();
        }
    }

    @Override
    @Transactional(rollbackFor = ServiceException.class)
    public Map<String, Object> nativePay(OrderInfoDto orderInfoDto) throws IOException {
        // 生成订单
        List<OrderItemDto> orderItems = orderInfoDto.getOrderItemList();
        // 调用统一下单 API
        HttpPost httpPost = new HttpPost(wechatPayConfig.getDomain().concat(WechatApiType.NATIVE_PAY.getType()));
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wechatPayConfig.getAppid());
        paramsMap.put("mchid", wechatPayConfig.getMchId());
        String description = "";
        for (OrderItemDto orderItemDto : orderItems) {
            if(orderItems.indexOf(orderItemDto) == (orderItems.size() - 1)) {
                description += orderItemDto.getSkuName() + " x" + orderItemDto.getSkuNum();
            } else {
                description += orderItemDto.getSkuName() + " x" + orderItemDto.getSkuNum() + ";";
            }
        }
        paramsMap.put("description", description);
        paramsMap.put("out_trade_no", orderInfoDto.getOrderNo());
        paramsMap.put("notify_url", wechatPayConfig.getNotifyDomain().concat(WechatNotifyType.NATIVE_NOTIFY.getType()));

        Map amountMap = new HashMap();
        // final int total = orderInfoDto.getTotalAmount().multiply(new BigDecimal("100")).setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
        amountMap.put("total", new BigDecimal(String.valueOf(orderInfoDto.getTotalAmount())).movePointRight(2).intValue());
        amountMap.put("currency", "CNY");

        paramsMap.put("amount", amountMap);

        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数为：{}", jsonParams);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");

        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpResponse response = wechatPayClient.execute(httpPost);

        try {

            String bodyAsString = EntityUtils.toString(response.getEntity());
            log.info("响应结果为：{}", bodyAsString);
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode == 200) {
                log.info("微信支付成功: {}", bodyAsString);
            } else if(statusCode == 204) {
                log.info("微信支付成功");
            } else {
                log.error("failed, resp code = {}, return body = {}", statusCode, bodyAsString);
                throw new ServerException("failed");
            }

            // 响应结果
            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            // 二维码
            String codeUrl = resultMap.get("code_url");
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfoDto.getOrderNo());
            return map;
        } finally {
            response.close();
        }
    }

    /**
     * 处理订单
     * @param bodyMap
     * @throws GeneralSecurityException
     */
    @Override
    @GlobalTransactional(rollbackFor = ServiceException.class)
    public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("处理订单");

        // 对称解密
        String plainText = decryptFromResource(bodyMap);

        // 将明文转换
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = (String) plainTextMap.get("out_trade_no");
        log.warn("处理订单号: {}", orderNo);
        OrderInfo orderInfo = orderFeignClient.getOrderInfoByOrderNo(orderNo);
        log.warn("处理订单项: {}", orderInfo);
        if(orderInfo.getOrderStatus() != OrderStatus.NOTPAY.getStatus()) {
            return;
        }
        // 更新订单状态
        // TODO
        log.info("订单号: {}; 更改订单状态为：{}", orderNo, OrderStatus.SUCCESS.getStatus());
        orderFeignClient.updateOrderStatus(orderNo, OrderStatus.SUCCESS.getStatus());
        // 记录支付日志
        // TODO
        PaymentInfoDto paymentInfo = new PaymentInfoDto();
        paymentInfo.setOrderNo(orderNo);
    }

    /**
     * 取消订单
     * @param orderNo
     */
    @Override
    public void cancelOrder(String orderNo) throws Exception {

        // 调用微信支付的关单接口
        this.closeOrder(orderNo);
        // 更新商户单的订单状态
        // TODO
    }

    /**
     * 查询订单
     * @param orderNo
     * @return
     */
    @Override
    public String queryOrder(String orderNo) throws Exception {
        log.info("查询订单: {}", orderNo);
        String url = String.format(WechatApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
        url = wechatPayConfig.getDomain().concat(url).concat("?mchid=").concat(wechatPayConfig.getMchId());
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        CloseableHttpResponse response = wechatPayClient.execute(httpGet);
        try {

            String bodyAsString = EntityUtils.toString(response.getEntity());
            log.info("响应结果为：{}", bodyAsString);
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode == 200) {
                log.info("微信支付成功: {}", bodyAsString);
            } else if(statusCode == 204) {
                log.info("微信支付成功");
            } else {
                log.error("failed, resp code = {}, return body = {}", statusCode, bodyAsString);
                throw new ServerException("failed");
            }

            return bodyAsString;
        } finally {
            response.close();
        }
    }

    /**
     * 根据订单号查询微信支付查单接口，核实订单状态
     * 如果订单已支付，则更新商户订单状态
     * 如果订单未支付，则调用关单接口关闭订单，并更新商户端订单状态
     * @param orderNo
     */
    @Override
    public void checkOrderStatus(String orderNo) throws Exception {
        // 调用微信支付查单接口
        String result = this.queryOrder(orderNo);

        Gson gson = new Gson();
        Map resultMap = gson.fromJson(result, HashMap.class);

        // 获取微信支付端的订单状态
        String tradeState = (String) resultMap.get("trade_state");
        if(WechatTradeState.SUCCESS.getType().equals(tradeState)) {
            // 订单已支付，更新商户订单状态
            // TODO 如果确认订单已支付则更新商户订单状态
            // TODO 更新支付日志
        }
        if(WechatTradeState.NOTPAY.getType().equals(tradeState)){
            // 订单未支付，调用关单接口关闭订单，并更新商户端订单状态
            this.closeOrder(orderNo);
            // TODO 更新本地订单状态
        }
    }

    /**
     * 申请退款
     * @param reason
     */
    @Override
    @Transactional(rollbackFor = ServiceException.class)
    public void refund(RefundReason reason) throws IOException {
        // TODO

        // 调用微信支付申请退款接口
        String url = wechatPayConfig.getDomain().concat(WechatApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);

        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("out_trade_no", reason.getOrderNo());
        // paramsMap.put("out_refund_no", ); // 退款单号
        paramsMap.put("reason", reason.getReason());
        paramsMap.put("notify_url", wechatPayConfig.getNotifyDomain().concat(WechatNotifyType.REFUND_NOTIFY.getType()));

        Map amountMap = new HashMap();
        // amountMap.put("refund", ); // 退款金额
        // amountMap.put("total", ); // 原订单金额
        amountMap.put("currency", "CNY");
        paramsMap.put("amount", amountMap);

        String jsonParams = gson.toJson(paramsMap);
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        CloseableHttpResponse response = wechatPayClient.execute(httpPost);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode == 200) {
                log.info("成功，退款返回结果: {}", bodyAsString);
            } else if(statusCode == 204) {
                log.info("成功");
            } else {
                log.error("failed, resp code = {}, return body = {}", statusCode, bodyAsString);
                throw new ServerException("failed");
            }
            // TODO 更新订单状态

            // TODO 更新退款单
        } finally {
            response.close();
        }
    }

    @Override
    public String queryRefund(String orderNo) throws IOException {
        log.info("查询退款: {}", orderNo);
        String url = String.format(WechatApiType.DOMESTIC_REFUNDS_QUERY.getType(), orderNo);
        url = wechatPayConfig.getDomain().concat(url);

        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        CloseableHttpResponse response = wechatPayClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode == 200) {
                log.info("成功，查询退款返回结果: {}", bodyAsString);
            } else if(statusCode == 204) {
                log.info("成功");
            } else {
                log.error("failed, resp code = {}, return body = {}", statusCode, bodyAsString);
                throw new ServerException("failed");
            }
            return bodyAsString;
        } finally {
            response.close();
        }
    }

    @Override
    @Transactional(rollbackFor = ServiceException.class)
    public void processRefund(Map<String, Object> bodyMap) throws GeneralSecurityException {
        String plainText = decryptFromResource(bodyMap);

        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String refundNo = (String) plainTextMap.get("out_refund_no");
        log.info("退款单号: {}", refundNo);
        if(lock.tryLock()) {
            try {
                // TODO 远程调用订单查询订单状态
//                if(OrderStatus.REFUND_PROCESSING.getStatus().equals(orderStatus)) {
//                    // 订单已退款，更新退款单状态
//                    return;
//                }

                // TODO 更新订单状态

                // TODO 更新退款单状态
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 关闭订单
     * @param orderNo
     */
    private void closeOrder(String orderNo) throws Exception {
        log.info("关闭订单: {}", orderNo);
        HttpPost httpPost = new HttpPost(wechatPayConfig.getDomain().concat(WechatApiType.CLOSE_ORDER_BY_NO.getType()));
        // 组装请求参数
        Gson gson = new Gson();
        Map<String, String> paramsMap = new HashMap();
        paramsMap.put("mchid", wechatPayConfig.getMchId());
        String jsonParams = gson.toJson(paramsMap);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        // 完成签名并执行请求
        CloseableHttpResponse response = wechatPayClient.execute(httpPost);

        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode == 200) {
                log.info("成功200");
            } else if(statusCode == 204) {
                log.info("成功204");
            } else {
                log.error("failed, resp code = {}, return body = {}", statusCode);
                throw new ServerException("failed");
            }
        } finally {
            response.close();
        }
    }

    /**
     * 对称解密
     */
    private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("密文解密");
        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
        // 密文
        String ciphertext = resourceMap.get("ciphertext");
        // 随机字符串
        String nonce = resourceMap.get("nonce");
        // 附加数据
        String associatedData = resourceMap.get("associated_data");
        log.info("密文：{}", ciphertext);
        AesUtil aesUtil = new AesUtil(wechatPayConfig.getApiV3Key().getBytes());
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);
        log.info("明文：{}", plainText);
        return plainText;
    }
}
